using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace DemoContentLoader
{
    public enum ContentType
    {
        Font = 1,
        Mesh = 2,
        Image = 3,
    }
    public interface IContent
    {
        ContentType ContentType { get; }
    }

    public class ContentArchive
    {
        private Dictionary<string, IContent> pathsToContent = new Dictionary<string, IContent>();

        public T Load<T>(string path)
        {
            if (!pathsToContent.TryGetValue(path, out var untypedContent))
            {
                throw new ArgumentException($"{path} not found in the content archive.");
            }
            if (untypedContent is T content)
            {
                return content;
            }
            else
            {
                throw new ArgumentException($"Content associated with {path} does not match the given type {typeof(T).Name}.");
            }
        }

        // 我们的内容类型非常有限。这不是通用发动机。我们可以快速修改一下,而不是有一本type>loader之类的字典。
        public static IContent Load(ContentType type, BinaryReader reader)
        {
            switch (type)
            {
                case ContentType.Font:
                    return FontIO.Load(reader);
                case ContentType.Mesh:
                    return MeshIO.Load(reader);
                case ContentType.Image:
                    return Texture2DIO.Load(reader);
            }
            throw new ArgumentException($"Given content type {type} cannot be loaded; no loader is specified. Is the archive corrupted?");
        }

        public static void Save(IContent content, BinaryWriter writer)
        {
            switch (content.ContentType)
            {
                case ContentType.Font:
                    FontIO.Save((FontContent)content, writer);
                    return;
                case ContentType.Mesh:
                    MeshIO.Save((MeshContent)content, writer);
                    return;
                case ContentType.Image:
                    Texture2DIO.Save((Texture2DContent)content, writer);
                    return;
            }
            throw new ArgumentException("Given content type cannot be saved; no archiver is specified.");
        }

        /// <summary>
        /// 从流中加载以前使用ContentArchive.Save保存的内容存档。
        /// </summary>
        /// <param name="stream">要从中加载的流。</param>
        /// <Returns>已加载内容的存档。</Returns>
        public static ContentArchive Load(Stream stream)
        {
            // 按顺序阅读每段内容。
            // 格式如下：
            // 条目计数
            // [条目1]
            // [条目2]
            // ..。
            // [条目N]

            // 其中条目：
            // [路径长度：int32]
            // [pathBytes：Byte[]]
            // [内容类型：int32]
            // [contentLengthInBytes：int32]
            // [内容：特定于串行器的内容]

            var archive = new ContentArchive();
            using (var reader = new BinaryReader(stream))
            {
                var entryCount = reader.ReadInt32();
                for (int i = 0; i < entryCount; ++i)
                {
                    var pathLengthInBytes = reader.ReadInt32();
                    byte[] pathBytes = new byte[pathLengthInBytes];
                    reader.Read(pathBytes, 0, pathLengthInBytes);
                    var path = Encoding.Unicode.GetString(pathBytes, 0, pathBytes.Length);

                    var contentType = (ContentType)reader.ReadInt32();
                    archive.pathsToContent.Add(path, Load(contentType, reader));
                }
            }
            return archive;
        }

        /// <summary>
        /// 以可加载为ContentArchive的格式保存一组路径-内容对。
        /// </summary>
        /// <param name="pathsToContent">要保存的路径-内容对。</param>
        /// <param name="stream">要保存到的输出流。</param>
        public static void Save(Dictionary<string, IContent> pathsToContent, Stream stream)
        {
            using (var writer = new BinaryWriter(stream))
            {
                writer.Write(pathsToContent.Count);
                foreach (var pair in pathsToContent)
                {
                    var path = pair.Key;
                    var content = pair.Value;

                    var pathBytes = Encoding.Unicode.GetBytes(path);
                    writer.Write(pathBytes.Length);
                    writer.Write(pathBytes);

                    writer.Write((int)content.ContentType);
                    Save(content, writer);
                }
            }
        }
    }
}
